
/*    This file is part of the managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.frontend.korona;

import java.util.Stack;
import java.util.LinkedList;
import org.managarm.jmanacclib.core.CclibReader;
import org.managarm.jmanacclib.core.CclibUnit;
import org.managarm.jmanacclib.core.CclibException;
import org.managarm.jmanacclib.core.CclibWriter;

public class KorScanner {
	// environment object used during the compilation process
	private final KorEnvironment environment;
	// list of all tokens
	private final LinkedList<KorToken> tokens = new LinkedList<KorToken>();
	// the reader object we get our input from
	private final CclibReader reader;
	private final Stack<Character> stack = new Stack<Character>();
	
	public KorScanner(final CclibReader input,
			final KorEnvironment envobject) throws CclibException {
		this.environment = envobject;
		
		// preprocess the input file
		final CclibWriter.BufferWriter buffer = new CclibWriter.BufferWriter();
		final KorPreprocessor preprocessor
				= new KorPreprocessor(environment, input, buffer);
		preprocessor.preprocess();
		this.reader = buffer.makeReader();

		scanStream();
	}
	
	public boolean endReached() {
		return tokens.size() == 0;
	}
	
	public KorToken fetch() {
		return tokens.removeFirst();
	}
	
	/* --------------------- scanning related functions ----------------------*/
	private final void scanStream() throws CclibException {
		tokens.add(new KorToken(KorToken.TYPE_CONTROL, "FirstToken",
				resolveUnit(), resolveLine()));
		
		while(endOfStream() == false) {
			final char c = fetchChar();
			
			// ignore whitespace
			if(c == '\n' || c == '\t' || c == ' ' || c == '\r')
				continue;
			
			// ignore all comments
			if(c == '/' && matchChar('/')) {
				// skip all characters before the next line feed
				while(fetchChar() != '\n') { }
				continue;
			}else if(c == '/' && matchChar('*')) {
				// skip all characters until the block comment ends
				while(true) {
					final char c2 = fetchChar();
					if(c2 == '*' && matchChar('/'))
						break;
				}
				continue;
			}
			
			final CclibUnit current_unit = resolveUnit();
			final int current_line = resolveLine();
			
			// process special tokens (2 character tokens)
			if(c == '+' && matchChar('+')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "++", 
						current_unit, current_line));
				continue;
			}else if(c == '-' && matchChar('-')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "--", 
						current_unit, current_line));
				continue;
			}else if(c == '+' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "+=", 
						current_unit, current_line));
				continue;
			}else if(c == '-' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "-=", 
						current_unit, current_line));
				continue;
			}else if(c == '*' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "*=", 
						current_unit, current_line));
				continue;
			}else if(c == '/' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "/=", 
						current_unit, current_line));
				continue;
			}else if(c == '%' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "%=", 
						current_unit, current_line));
				continue;
			}else if(c == '|' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "|=", 
						current_unit, current_line));
				continue;
			}else if(c == '&' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "&=", 
						current_unit, current_line));
				continue;
			}else if(c == '<' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "<=", 
						current_unit, current_line));
				continue;
			}else if(c == '>' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, ">=", 
						current_unit, current_line));
				continue;
			}else if(c == '=' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "==", 
						current_unit, current_line));
				continue;
			}else if(c == '!' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "!=", 
						current_unit, current_line));
				continue;
			}else if(c == '^' && matchChar('=')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "^=",
						current_unit, current_line));
				continue;
			}else if(c == '<' && matchChar('<')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "<<", 
						current_unit, current_line));
				continue;
			}else if(c == '>' && matchChar('>')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, ">>", 
						current_unit, current_line));
				continue;
			}else if(c == '&' && matchChar('&')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "&&",
						current_unit, current_line));
				continue;
			}else if(c == '|' && matchChar('|')) {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "||",
						current_unit, current_line));
				continue;
			}
			
			// process special tokens (1 character tokens)
			if(c == '.' || c == ':' || c == ',' || c == ';'
					|| c == '{' || c == '}' || c == '(' || c == ')'
					|| c == '[' || c == ']' || c == '$' || c == '?'
					|| c == '+' || c == '-' || c == '*' || c == '/'
					|| c == '%' || c == '|' || c == '&' || c == '='
					|| c == '<' || c == '>' || c == '!' || c == '^') {
				tokens.add(new KorToken(KorToken.TYPE_SPECIAL, "" + c,
						current_unit, current_line));
				continue;
			}
			
			// process escaped identifiers
			if(c == '§') {
				final StringBuffer buffer = new StringBuffer();
				while(true) {
					final char litchar = fetchChar();
					if(litchar == '§')
						break;
					if(litchar == '\\') { // process escape codes
						buffer.append(processEscapeCode());
					}else{
						buffer.append(litchar);
					}
				}
				tokens.add(new KorToken(KorToken.TYPE_IDENTIFIER,
						buffer.toString(), current_unit, current_line));
				continue;
			}
			
			// process character literals
			if(c == '\'') {
				char litchar = fetchChar();
				
				// process escape codes
				if(litchar == '\\') {
					tokens.add(new KorToken(KorToken.TYPE_CONST_CHAR,
							processEscapeCode(), current_unit, current_line));
				}else{
					tokens.add(new KorToken(KorToken.TYPE_CONST_CHAR,
							"" + litchar, current_unit, current_line));
				}
				
				// make sure the ending sequence is correct
				if(fetchChar() != '\'')
					throw new CclibException("Syntax error: " +
							"Illegal character literal", 
							current_unit, current_line);
				continue;
			}
			
			// process string literals
			if(c == '"') {
				final StringBuffer buffer = new StringBuffer();
				while(true) {
					final char litchar = fetchChar();
					if(litchar == '"')
						break;
					
					// process escape codes
					if(litchar == '\\') {
						buffer.append(processEscapeCode());
					}else{
						buffer.append(litchar);
					}
				}
				
				tokens.add(new KorToken(KorToken.TYPE_CONST_STRING,
						buffer.toString(), current_unit, current_line));
				continue;
			}
			
			// process hexadecimal literals
			if(c == '0' && matchChar('x')) {
				final StringBuffer buffer = new StringBuffer();
				while(true) {
					final char litchar = fetchChar();
					if((litchar < '0' || litchar > '9')
							&& (litchar < 'a' || litchar > 'f')
							&& (litchar < 'A' || litchar > 'F')) {
						pushChar(litchar);
						break;
					}
					buffer.append(litchar);
				}
				
				// convert hexadecimal to decimal numbers
				final long number = Long.parseLong(buffer.toString(), 16);
				tokens.add(new KorToken(KorToken.TYPE_CONST_NUMERIC,
						Long.toString(number), current_unit, current_line));
				continue;
			}
			
			// process decimal literals
			if(c >= '0' && c <= '9') {
				final StringBuffer buffer = new StringBuffer();
				buffer.append(c);
				while(true) {
					final char litchar = fetchChar();
					if((litchar < '0' || litchar > '9')) {
						pushChar(litchar);
						break;
					}
					buffer.append(litchar);
				}

				tokens.add(new KorToken(KorToken.TYPE_CONST_NUMERIC,
						buffer.toString(), current_unit, current_line));
				continue;
			}
			
			// process identifiers
			if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
				final StringBuffer buffer = new StringBuffer();
				buffer.append(c);
				while(true) {
					final char litchar = fetchChar();
					if((litchar < '0' || litchar > '9')
							&& (litchar < 'a' || litchar > 'z')
							&& (litchar < 'A' || litchar > 'Z')
							&& litchar != '_') {
						pushChar(litchar);
						break;
					}
					
					buffer.append(litchar);
				}

				tokens.add(new KorToken(KorToken.TYPE_IDENTIFIER,
						buffer.toString(), current_unit, current_line));
				continue;
			}
			
			throw new CclibException("Syntax error: Unrecognized " +
					"character: " + c, resolveUnit(), resolveLine());
		}
	}
	
	private String processEscapeCode() throws CclibException {
		final char c = fetchChar();
		if(c == '0') return "\0";
		if(c == 'n') return "\n";
		if(c == 'b') return "\b";
		if(c == 't') return "\t";
		if(c == '"') return "\"";
		if(c == '\\') return "\\";
		
		if(c == 'u') {
			String number = new String();
			number += fetchChar();
			number += fetchChar();
			number += fetchChar();
			number += fetchChar();
			return "" + (char)Integer.parseInt(number, 16);
		}

		throw new CclibException("Syntax error: Illegal escape sequence", 
				resolveUnit(), resolveLine());
	}
	
	/* ---------------- high level character access functions ---------------*/
	// if the next character equals "c" we fetch it and return true
	private boolean matchChar(final char c) throws CclibException {
		final char r = fetchChar();
		if(r != c) {
			pushChar(r);
			return false;
		}else{
			return true;
		}
	}
	

	/* ----------------- low level character access functions ---------------*/
	private char fetchChar() throws CclibException {
		if(stack.empty() == false)
			return stack.pop();
		return reader.fetch();
	}
	
	private void pushChar(final char c) throws CclibException {
		stack.push(c);
	}
	
	private boolean endOfStream() throws CclibException {
		if(stack.empty() == false)
			return false;
		return reader.endReached();
	}
	
	private CclibUnit resolveUnit() throws CclibException {
		return reader.getUnit();
	}
	
	private int resolveLine() throws CclibException {
		int disp = 0;
		for(int i = 0; i < stack.size(); i++)
			if(stack.get(i) == '\n')
				disp--;
		return reader.getLine() + disp;
	}
}
